<template>
  <div class="canvas" ref="canvasRef" @wheel="$event => handleMousewheelCanvas($event)"
    @mousedown="$event => handleClickBlankArea($event)" v-contextmenu="contextmenuCanvas"
    v-click-outside="removeEditorAreaFocus">
    <!-- 该组件只在有creatingElement的时候才展示，如文本框、形状、线条 -->
    <ElementCreateSelection v-if="creatingElement" @created="data => insertElementFromCreateSelection(data)" />
    <!-- 画布可视区域 -->
    <div class="viewport-wrapper" :style="{
      width: viewportStyles.width * canvasScale + 'px',
      height: viewportStyles.height * canvasScale + 'px',
      left: viewportStyles.left + 'px',
      top: viewportStyles.top + 'px'
    }">
      <div class="operates">
        <!-- 吸附线 -->
        <AlignmentLine v-for="(line, index) in alignmentLines" :key="index" :type="line.type" :axis="line.axis"
          :length="line.length" :canvasScale="canvasScale" />
        <MultiSelectOperate v-if="activeElementIdList.length > 1" :elementList="elementList"
          :scaleMultiElement="scaleMultiElement" />
        <!-- 元素可以调节大小（选中可以出现边框线以及八个锚点）、边框线、旋转角度 -->
        <Operate v-for="element in elementList" :key="element.id" :elementInfo="element"
          :isSelected="activeElementIdList.includes(element.id)" :isActive="handleElementId === element.id"
          :isActiveGroupElement="activeGroupElementId === element.id" :isMultiSelect="activeElementIdList.length > 1"
          :rotateElement="rotateElement" :scaleElement="scaleElement" :openLinkDialog="openLinkDialog"
          :dragLineElement="dragLineElement" :moveShapeKeypoint="moveShapeKeypoint"
          v-show="!hiddenElementIdList.includes(element.id)" />
        <!-- 可视区域的背景 -->
        <ViewportBackground />
      </div>
      <div class="viewport" ref="viewportRef" :style="{ transform: `scale(${canvasScale})` }">
        <!-- 鼠标的选区范围 -->
        <MouseSelection v-if="mouseSelectionVisible" :top="mouseSelection.top" :left="mouseSelection.left"
          :width="mouseSelection.width" :height="mouseSelection.height" :quadrant="mouseSelectionQuadrant" />
        <!-- 元素可以移动、出现边框、阴影、不透明度等 -->
        <EditableElement v-for="(element, index) in elementList" :key="element.id" :elementInfo="element"
          :elementIndex="index + 1" :isMultiSelect="activeElementIdList.length > 1" :selectElement="selectElement"
          :openLinkDialog="openLinkDialog" v-show="!hiddenElementIdList.includes(element.id)" />
      </div>
    </div>

    <div class="drag-mask" v-if="spaceKeyState"></div>

    <Ruler :viewportStyles="viewportStyles" v-if="showRuler" />

    <!-- 为元素设置链接时 -->
    <Modal v-model:visible="linkDialogVisible" :footer="null" centered :width=540 destroyOnClose>
      <LinkDialog @close="linkDialogVisible = false" />
    </Modal>
  </div>
</template>

<script setup lang="ts">
import { ref, provide, watchEffect } from 'vue'
import { storeToRefs } from 'pinia'
import { throttle } from 'lodash'
import { KEYS } from '@/configs/hotkey'
import { PPTElement } from '@/types/slides'
import { injectKeySlideScale } from '@/types/injectKey'
import { AlignmentLineProps } from '@/types/edit'
import { removeAllRanges } from '@/utils/selection'
import { ContextmenuItem } from '@/components/Contextmenu/types'
import { Modal } from 'ant-design-vue'

import { useMainStore } from '@/store/main'
import { useKeyboardStore } from '@/store/keyboard'
import { useSlidesStore } from '@/store/slides'

import useMouseSelection from './hooks/useMouseSelections'
import useViewportSize from './hooks/useViewportSize'
import useInsertFromCreateSelection from './hooks/useInsertFromCreateSelection'
import useSelectElement from './hooks/useSelectElement'
import useDragElement from './hooks/useDragElement'
import useScaleElement from './hooks/useScaleElement'
import useDragLineElement from './hooks/useDragLineElement'
import useRotateElement from './hooks/useRotateElement'
import useMoveShapeKeypoint from './hooks/useMoveShapeKeypoint'

import useScaleCanvas from '@/hooks/useScaleCanvas'
import useSlideHandler from '@/hooks/useSlideHandler'
import useCopyAndPasteElement from '@/hooks/useCopyAndPasteElement'
import useSelectAllElement from '@/hooks/useSelectAllElement'
import useScreening from '@/hooks/useScreening'
import useDeleteElement from '@/hooks/useDeleteElement'

import ElementCreateSelection from './ElementCreateSelection.vue'
import Ruler from './Ruler.vue'
import LinkDialog from './LinkDialog.vue'
import ViewportBackground from './ViewportBackground.vue'
import EditableElement from './EditableElement.vue'
import MouseSelection from './MouseSelection.vue'
import AlignmentLine from './AlignmentLine.vue'
import MultiSelectOperate from './Operate/MultiSelectOperate.vue'
import Operate from './Operate/index.vue'

const mainStore = useMainStore()
const {
  activeElementIdList,
  editorAreaFocus,
  textFormatPainter,
  showRuler,
  gridLineSize,
  showSelectPanel,
  canvasScale,
  creatingElement,
  hiddenElementIdList,
  handleElementId,
  activeGroupElementId
} = storeToRefs(mainStore)
const { ctrlKeyState, spaceKeyState } = storeToRefs(useKeyboardStore())
const { currentSlide } = storeToRefs(useSlidesStore())

const { updateSlideIndex } = useSlideHandler()
const { pasteElement } = useCopyAndPasteElement()
const { selectAllElement } = useSelectAllElement()
const { enterScreeningFromStart } = useScreening()
const { deleteAllElements } = useDeleteElement()

const elementList = ref<PPTElement[]>([])
const viewportRef = ref<HTMLElement>()
const canvasRef = ref<HTMLElement>()
const alignmentLines = ref<AlignmentLineProps[]>([])

const linkDialogVisible = ref(false)
const openLinkDialog = () => linkDialogVisible.value = true

const setLocalElementList = () => {
  elementList.value = currentSlide.value ? JSON.parse(JSON.stringify(currentSlide.value.elements)) : []
}
// 立即运行一个函数，同时响应式地追踪其依赖，并在依赖更改时重新执行
watchEffect(setLocalElementList)

const {
  mouseSelection,
  mouseSelectionVisible,
  mouseSelectionQuadrant,
  updateMouseSelection
} = useMouseSelection(elementList, viewportRef)
const { dragViewport, viewportStyles } = useViewportSize(canvasRef)
// 在鼠标绘制的范围插入元素
const { insertElementFromCreateSelection } = useInsertFromCreateSelection(viewportRef)
const { dragElement } = useDragElement(elementList, alignmentLines, canvasScale)
// 处理选择的元素
const { selectElement } = useSelectElement(elementList, dragElement)
const { scaleElement, scaleMultiElement } = useScaleElement(elementList, alignmentLines, canvasScale)
const { dragLineElement } = useDragLineElement(elementList)
const { rotateElement } = useRotateElement(elementList, viewportRef)
const { moveShapeKeypoint } = useMoveShapeKeypoint(elementList, canvasScale)

// 滚动鼠标
const { scaleCanvas } = useScaleCanvas()
// throttle：节流函数，在n秒内只执行一次
const throttleScaleCanvas = throttle(scaleCanvas, 100, { leading: true, trailing: false })
const throttleUpdateSlideIndex = throttle(updateSlideIndex, 300, { leading: true, trailing: false })
const handleMousewheelCanvas = (e: WheelEvent) => {
  // 阻止网页的默认事件（按住ctrl+滚轮，会缩放网页）
  e.preventDefault()
  // 按住ctrl键时：缩放画布（本质是改变canvasPercentage）
  if (ctrlKeyState.value) {
    // e.deltaY：只读属性是一个 double 类型值，声明垂直滚动量
    if (e.deltaY > 0) throttleScaleCanvas('-')
    else if (e.deltaY < 0) throttleScaleCanvas('+')
  }
  // 上下翻页
  else {
    if (e.deltaY > 0) throttleUpdateSlideIndex(KEYS.DOWN)
    else if (e.deltaY < 0) throttleUpdateSlideIndex(KEYS.UP)
  }
}

// 移除画布编辑区域焦点
const removeEditorAreaFocus = () => {
  if (editorAreaFocus.value) mainStore.setEditorAreaFocus(false)
}

// 点击画布的空白区域：清空焦点元素、设置画布焦点、清除文字选区、清空格式刷状态
const handleClickBlankArea = (e: MouseEvent) => {
  if (activeElementIdList.value.length) mainStore.setActiveElementIdList([])

  if (!spaceKeyState.value) updateMouseSelection(e)
  else dragViewport(e)

  if (!editorAreaFocus.value) mainStore.setEditorAreaFocus(true)
  if (textFormatPainter.value) mainStore.setTextFormatPainter(null)
  // 清除文字选区
  removeAllRanges()
}

// 开关标尺
const toggleRuler = () => {
  mainStore.setRulerState(!showRuler.value)
}

const contextmenuCanvas = (): ContextmenuItem[] => {
  return [
    {
      text: '粘贴',
      subText: 'Ctrl + V',
      handler: pasteElement,
    },
    {
      text: '全选',
      subText: 'Ctrl + A',
      handler: selectAllElement,
    },
    {
      text: '标尺',
      subText: showRuler.value ? '√' : '',
      handler: toggleRuler,
    },
    {
      text: '网格线',
      handler: () => mainStore.setGridLineSize(gridLineSize.value ? 0 : 50),
      children: [
        {
          text: '无',
          subText: gridLineSize.value === 0 ? '√' : '',
          handler: () => mainStore.setGridLineSize(0),
        },
        {
          text: '小',
          subText: gridLineSize.value === 25 ? '√' : '',
          handler: () => mainStore.setGridLineSize(25),
        },
        {
          text: '中',
          subText: gridLineSize.value === 50 ? '√' : '',
          handler: () => mainStore.setGridLineSize(50),
        },
        {
          text: '大',
          subText: gridLineSize.value === 100 ? '√' : '',
          handler: () => mainStore.setGridLineSize(100),
        },
      ]
    },
    {
      text: showSelectPanel.value ? '关闭选择面板' : '打开选择面板',
      handler: () => {
        if (!showSelectPanel.value) mainStore.setSelectPanelState(true)
        else mainStore.setSelectPanelState(false)
      },
    },
    {
      text: '重置当前页',
      handler: deleteAllElements,
    },
    { divider: true },
    {
      text: '幻灯片放映',
      subText: 'F5',
      handler: enterScreeningFromStart,
    },
  ]
}

provide(injectKeySlideScale, canvasScale)
</script>

<style lang="scss" scoped>
.canvas {
  height: 100%;
  user-select: none;
  overflow: hidden;
  background-color: $lightGray;
  position: relative;
}

.drag-mask {
  cursor: grab;
  @include absolute-0();
}

.viewport-wrapper {
  position: absolute;
  box-shadow: 0 0 .9375rem 0 rgba(0, 0, 0, 0.1);
}

.viewport {
  position: absolute;
  top: 0;
  left: 0;
  transform-origin: 0 0;
}
</style>